1   package org.apache.solr.util;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements.  See the NOTICE file distributed with
6    * this work for additional information regarding copyright ownership.
7    * The ASF licenses this file to You under the Apache License, Version 2.0
8    * (the "License"); you may not use this file except in compliance with
9    * the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  import javax.crypto.BadPaddingException;
21  import javax.crypto.Cipher;
22  import javax.crypto.IllegalBlockSizeException;
23  import javax.crypto.spec.IvParameterSpec;
24  import javax.crypto.spec.SecretKeySpec;
25  
26  import java.lang.invoke.MethodHandles;
27  import java.nio.ByteBuffer;
28  import java.nio.charset.Charset;
29  import java.nio.charset.StandardCharsets;
30  import java.security.GeneralSecurityException;
31  import java.security.InvalidKeyException;
32  import java.security.KeyFactory;
33  import java.security.KeyPairGenerator;
34  import java.security.MessageDigest;
35  import java.security.NoSuchAlgorithmException;
36  import java.security.PrivateKey;
37  import java.security.PublicKey;
38  import java.security.SecureRandom;
39  import java.security.Signature;
40  import java.security.SignatureException;
41  import java.security.spec.X509EncodedKeySpec;
42  import java.util.Arrays;
43  import java.util.HashMap;
44  import java.util.Map;
45  
46  import org.apache.solr.common.SolrException;
47  import org.apache.solr.common.util.Base64;
48  import org.slf4j.Logger;
49  import org.slf4j.LoggerFactory;
50  
51  /**A utility class to verify signatures
52   *
53   */
54  public final class CryptoKeys {
55    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
56    private final Map<String, PublicKey> keys;
57    private Exception exception;
58  
59    public CryptoKeys(Map<String, byte[]> trustedKeys) throws Exception {
60      HashMap<String, PublicKey> m = new HashMap<>();
61      for (Map.Entry<String, byte[]> e : trustedKeys.entrySet()) {
62        m.put(e.getKey(), getX509PublicKey(e.getValue()));
63  
64      }
65      this.keys = m;
66    }
67  
68    /**
69     * Try with all signatures and return the name of the signature that matched
70     */
71    public String verify(String sig, ByteBuffer data) {
72      exception = null;
73      for (Map.Entry<String, PublicKey> entry : keys.entrySet()) {
74        boolean verified;
75        try {
76          verified = CryptoKeys.verify(entry.getValue(), Base64.base64ToByteArray(sig), data);
77          log.info("verified {} ", verified);
78          if (verified) return entry.getKey();
79        } catch (Exception e) {
80          exception = e;
81          log.info("NOT verified  ");
82        }
83  
84      }
85  
86      return null;
87    }
88  
89  
90    /**
91     * Create PublicKey from a .DER file
92     */
93    public static PublicKey getX509PublicKey(byte[] buf)
94        throws Exception {
95      X509EncodedKeySpec spec = new X509EncodedKeySpec(buf);
96      KeyFactory kf = KeyFactory.getInstance("RSA");
97      return kf.generatePublic(spec);
98    }
99  
100   /**
101    * Verify the signature of a file
102    *
103    * @param publicKey the public key used to sign this
104    * @param sig       the signature
105    * @param data      The data tha is signed
106    */
107   public static boolean verify(PublicKey publicKey, byte[] sig, ByteBuffer data) throws InvalidKeyException, SignatureException {
108     int oldPos = data.position();
109     Signature signature = null;
110     try {
111       signature = Signature.getInstance("SHA1withRSA");
112       signature.initVerify(publicKey);
113       signature.update(data);
114       boolean verify = signature.verify(sig);
115       return verify;
116 
117     } catch (NoSuchAlgorithmException e) {
118       //will not happen
119     } finally {
120       //Signature.update resets the position. set it back to old
121       data.position(oldPos);
122     }
123     return false;
124   }
125 
126   private static byte[][] evpBytesTokey(int key_len, int iv_len, MessageDigest md,
127                                         byte[] salt, byte[] data, int count) {
128     byte[][] both = new byte[2][];
129     byte[] key = new byte[key_len];
130     int key_ix = 0;
131     byte[] iv = new byte[iv_len];
132     int iv_ix = 0;
133     both[0] = key;
134     both[1] = iv;
135     byte[] md_buf = null;
136     int nkey = key_len;
137     int niv = iv_len;
138     int i = 0;
139     if (data == null) {
140       return both;
141     }
142     int addmd = 0;
143     for (; ; ) {
144       md.reset();
145       if (addmd++ > 0) {
146         md.update(md_buf);
147       }
148       md.update(data);
149       if (null != salt) {
150         md.update(salt, 0, 8);
151       }
152       md_buf = md.digest();
153       for (i = 1; i < count; i++) {
154         md.reset();
155         md.update(md_buf);
156         md_buf = md.digest();
157       }
158       i = 0;
159       if (nkey > 0) {
160         for (; ; ) {
161           if (nkey == 0)
162             break;
163           if (i == md_buf.length)
164             break;
165           key[key_ix++] = md_buf[i];
166           nkey--;
167           i++;
168         }
169       }
170       if (niv > 0 && i != md_buf.length) {
171         for (; ; ) {
172           if (niv == 0)
173             break;
174           if (i == md_buf.length)
175             break;
176           iv[iv_ix++] = md_buf[i];
177           niv--;
178           i++;
179         }
180       }
181       if (nkey == 0 && niv == 0) {
182         break;
183       }
184     }
185     for (i = 0; i < md_buf.length; i++) {
186       md_buf[i] = 0;
187     }
188     return both;
189   }
190 
191   public static String decodeAES(String base64CipherTxt, String pwd) {
192     int[] strengths = new int[]{256, 192, 128};
193     Exception e = null;
194     for (int strength : strengths) {
195       try {
196         return decodeAES(base64CipherTxt, pwd, strength);
197       } catch (Exception exp) {
198         e = exp;
199       }
200     }
201     throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error decoding ", e);
202   }
203 
204 
205   public static String decodeAES(String base64CipherTxt, String pwd, final int keySizeBits) {
206     final Charset ASCII = Charset.forName("ASCII");
207     final int INDEX_KEY = 0;
208     final int INDEX_IV = 1;
209     final int ITERATIONS = 1;
210     final int SALT_OFFSET = 8;
211     final int SALT_SIZE = 8;
212     final int CIPHERTEXT_OFFSET = SALT_OFFSET + SALT_SIZE;
213 
214     try {
215       byte[] headerSaltAndCipherText = Base64.base64ToByteArray(base64CipherTxt);
216 
217       // --- extract salt & encrypted ---
218       // header is "Salted__", ASCII encoded, if salt is being used (the default)
219       byte[] salt = Arrays.copyOfRange(
220           headerSaltAndCipherText, SALT_OFFSET, SALT_OFFSET + SALT_SIZE);
221       byte[] encrypted = Arrays.copyOfRange(
222           headerSaltAndCipherText, CIPHERTEXT_OFFSET, headerSaltAndCipherText.length);
223 
224       // --- specify cipher and digest for evpBytesTokey method ---
225 
226       Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
227       MessageDigest md5 = MessageDigest.getInstance("MD5");
228 
229       // --- create key and IV  ---
230 
231       // the IV is useless, OpenSSL might as well have use zero's
232       final byte[][] keyAndIV = evpBytesTokey(
233           keySizeBits / Byte.SIZE,
234           aesCBC.getBlockSize(),
235           md5,
236           salt,
237           pwd.getBytes(ASCII),
238           ITERATIONS);
239 
240       SecretKeySpec key = new SecretKeySpec(keyAndIV[INDEX_KEY], "AES");
241       IvParameterSpec iv = new IvParameterSpec(keyAndIV[INDEX_IV]);
242 
243       // --- initialize cipher instance and decrypt ---
244 
245       aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
246       byte[] decrypted = aesCBC.doFinal(encrypted);
247       return new String(decrypted, ASCII);
248     } catch (BadPaddingException e) {
249       // AKA "something went wrong"
250       throw new IllegalStateException(
251           "Bad password, algorithm, mode or padding;" +
252               " no salt, wrong number of iterations or corrupted ciphertext.", e);
253     } catch (IllegalBlockSizeException e) {
254       throw new IllegalStateException(
255           "Bad algorithm, mode or corrupted (resized) ciphertext.", e);
256     } catch (GeneralSecurityException e) {
257       throw new IllegalStateException(e);
258     }
259   }
260 
261   public static PublicKey deserializeX509PublicKey(String pubKey) {
262     try {
263       KeyFactory keyFactory = KeyFactory.getInstance("RSA");
264       X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.base64ToByteArray(pubKey));
265       return keyFactory.generatePublic(publicKeySpec);
266     } catch (Exception e) {
267       throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,e);
268     }
269   }
270 
271   public static byte[] decryptRSA(byte[] buffer, PublicKey pubKey) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
272     Cipher rsaCipher = null;
273     try {
274       rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
275     } catch (Exception e) {
276       throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,e);
277     }
278     rsaCipher.init(Cipher.DECRYPT_MODE, pubKey);
279     return rsaCipher.doFinal(buffer, 0, buffer.length);
280 
281   }
282 
283   public static class RSAKeyPair {
284     private final String pubKeyStr;
285     private final PublicKey publicKey;
286     private final PrivateKey privateKey;
287     private final SecureRandom random = new SecureRandom();
288 
289 
290     public RSAKeyPair() {
291       KeyPairGenerator keyGen = null;
292       try {
293         keyGen = KeyPairGenerator.getInstance("RSA");
294       } catch (NoSuchAlgorithmException e) {
295         throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
296       }
297       keyGen.initialize(512);
298       java.security.KeyPair keyPair = keyGen.genKeyPair();
299       privateKey = keyPair.getPrivate();
300       publicKey = keyPair.getPublic();
301 
302       X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
303           publicKey.getEncoded());
304 
305       pubKeyStr = Base64.byteArrayToBase64(x509EncodedKeySpec.getEncoded());
306     }
307 
308     public String getPublicKeyStr() {
309       return pubKeyStr;
310     }
311 
312     public PublicKey getPublicKey() {
313       return publicKey;
314     }
315 
316     public byte[] encrypt(ByteBuffer buffer) {
317       try {
318         Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
319         rsaCipher.init(Cipher.ENCRYPT_MODE, privateKey);
320         return rsaCipher.doFinal(buffer.array(),buffer.position(), buffer.limit());
321       } catch (Exception e) {
322         throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,e);
323       }
324     }
325     public byte[] signSha256(byte[] bytes) throws InvalidKeyException, SignatureException {
326       Signature dsa = null;
327       try {
328         dsa = Signature.getInstance("SHA256withRSA");
329       } catch (NoSuchAlgorithmException e) {
330         throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
331       }
332       dsa.initSign(privateKey);
333       dsa.update(bytes,0,bytes.length);
334       return dsa.sign();
335 
336     }
337 
338   }
339 
340   public static void main(String[] args) throws Exception {
341     RSAKeyPair keyPair = new RSAKeyPair();
342     System.out.println(keyPair.getPublicKeyStr());
343     PublicKey pk = deserializeX509PublicKey(keyPair.getPublicKeyStr());
344     byte[] payload = "Hello World!".getBytes(StandardCharsets.UTF_8);
345     byte[] encrypted = keyPair.encrypt(ByteBuffer.wrap(payload));
346     String cipherBase64 = Base64.byteArrayToBase64(encrypted);
347     System.out.println("encrypted: "+ cipherBase64);
348     System.out.println("signed: "+ Base64.byteArrayToBase64(keyPair.signSha256(payload)));
349     System.out.println("decrypted "+  new String(decryptRSA(encrypted , pk), StandardCharsets.UTF_8));
350   }
351 
352 }